<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="wX6sX" id="wX6sX"><span data-lake-id="u21d15bea" id="u21d15bea" style="color: rgba(0, 0, 0, 0.87)">典型回答</span></h1>
  <p data-lake-id="u08c9a474" id="u08c9a474"><br></p>
  <p data-lake-id="ua5ef6e1f" id="ua5ef6e1f"><span data-lake-id="u37ba09fc" id="u37ba09fc">LongAdder是</span><span data-lake-id="u983d5320" id="u983d5320" class="lake-fontsize-12" style="color: rgba(0, 0, 0, 0.87)">Java 8中推出了一个新的类，主要是为了解决</span><span data-lake-id="ucf18de89" id="ucf18de89">AtomicLong在多线程竞争激烈的情况下性能并不高的问题。它主要是采用</span><span data-lake-id="u2babca66" id="u2babca66" class="lake-fontsize-12" style="color: rgba(0, 0, 0, 0.87)">分段+CAS的方式来提升原子操作的性能。</span></p>
  <p data-lake-id="u1e5d4a55" id="u1e5d4a55"><span data-lake-id="ufc97c8cb" id="ufc97c8cb" class="lake-fontsize-12" style="color: rgba(0, 0, 0, 0.87)">​</span><br></p>
  <p data-lake-id="u3a5463bb" id="u3a5463bb"><span data-lake-id="ucbd74ddb" id="ucbd74ddb" class="lake-fontsize-12" style="color: rgba(0, 0, 0, 0.87)">相比于</span><span data-lake-id="ud681c9bc" id="ud681c9bc">AtomicLong，LongAdder有更好的性能，但是LongAdder是典型的以空间换时间的实现方式，所以他所需要用到的空间更大， 而且LongAdder可能存在结果不准确的问题，而AtomicLong并不会。</span></p>
  <p data-lake-id="uac7b16c6" id="uac7b16c6"><span data-lake-id="u217d8c46" id="u217d8c46">​</span><br></p>
  <h1 data-lake-id="vTcnD" id="vTcnD"><span data-lake-id="u6d79696b" id="u6d79696b">扩展知识</span></h1>
  <p data-lake-id="ue922fe99" id="ue922fe99"><br></p>
  <h2 data-lake-id="oiOdt" id="oiOdt"><span data-lake-id="u51644354" id="u51644354">AtomicLong实现原理</span></h2>
  <p data-lake-id="uc505be46" id="uc505be46"><br></p>
  <p data-lake-id="u861a58f7" id="u861a58f7"><span data-lake-id="u782e3772" id="u782e3772">AtomicLong和AtomicInteger、AtomicDouble&#x0;等其他原子操作的类一样，都是基于Unsafe&#x0;实现的。Unsafe&#x0;是一个用来进行硬件级别的原子操作的工具类。在AtomicLong&#x0;中定义如下：</span></p>
  <p data-lake-id="u51485405" id="u51485405"><span data-lake-id="ubcde3ab2" id="ubcde3ab2">​</span><br></p>
  <pre lang="java"><code>
private static final Unsafe unsafe = Unsafe.getUnsafe();
</code></pre>
  <p data-lake-id="u33de424f" id="u33de424f"><br></p>
  <p data-lake-id="uee1dafe4" id="uee1dafe4"><span data-lake-id="u4522934d" id="u4522934d">并且通过在类中定义一个volatile的变量用来计数：</span></p>
  <p data-lake-id="ufd0ea83a" id="ufd0ea83a"><span data-lake-id="u42ffc8ed" id="u42ffc8ed">​</span><br></p>
  <pre lang="java"><code>
private volatile long value;
</code></pre>
  <p data-lake-id="uce653ba2" id="uce653ba2"><span data-lake-id="u00dba054" id="u00dba054">​</span><br></p>
  <p data-lake-id="u81337613" id="u81337613"><span data-lake-id="ub379ef46" id="ub379ef46">以下几个常用的方法具体实现如下：</span></p>
  <p data-lake-id="u71395d3e" id="u71395d3e"><span data-lake-id="ua08e51c1" id="ua08e51c1">​</span><br></p>
  <pre lang="java"><code>
    public final long incrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
    }


    public final long addAndGet(long delta) {
        return unsafe.getAndAddLong(this, valueOffset, delta) + delta;
    }

    public final boolean compareAndSet(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }

		public final long getAndSet(long newValue) {
        return unsafe.getAndSetLong(this, valueOffset, newValue);
    }
</code></pre>
  <p data-lake-id="udff64de7" id="udff64de7"><br></p>
  <p data-lake-id="u0f70f708" id="u0f70f708"><span data-lake-id="u1ef12bcf" id="u1ef12bcf">可以看到，都是基于unsafe来实现的，其实就是调用了底层的CAS操作来进行原子操作的。</span></p>
  <p data-lake-id="u499fdfb6" id="u499fdfb6"><span data-lake-id="ue42eaae7" id="ue42eaae7">​</span><br></p>
  <h2 data-lake-id="EyjaQ" id="EyjaQ"><span data-lake-id="u92a57d90" id="u92a57d90">LongAdder实现原理</span></h2>
  <p data-lake-id="u16111dcf" id="u16111dcf"><br></p>
  <p data-lake-id="u7f9a8e74" id="u7f9a8e74"><span data-lake-id="u60310790" id="u60310790">JDK 1.8中的LongAdder继承自抽象类java.util.concurrent.atomic.Striped64&#x0;的，这个类也是JDK 1.8中新增的，他主要就是用来在给并发场景中提供计数支持的。</span></p>
  <p data-lake-id="u8c689651" id="u8c689651"><span data-lake-id="ube76625b" id="ube76625b">​</span><br></p>
  <p data-lake-id="uca2d2ec6" id="uca2d2ec6"><span data-lake-id="u58399d2c" id="u58399d2c">Striped64的设计思路和ConcurrentHashMap类似，都是希望通过分散竞争的方式来提升并发的性能。再具体是线上，主要依赖了其中的以下两个字段：</span></p>
  <p data-lake-id="u72ee5132" id="u72ee5132"><span data-lake-id="u231f9236" id="u231f9236">​</span><br></p>
  <pre lang="java"><code>

    /**
     * Table of cells. When non-null, size is a power of 2.
     */
    transient volatile Cell[] cells;

    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     */
    transient volatile long base;

</code></pre>
  <p data-lake-id="u0759eec9" id="u0759eec9"><br></p>
  <p data-lake-id="u3b861032" id="u3b861032"><br></p>
  <p data-lake-id="u2c94fc5b" id="u2c94fc5b"><span data-lake-id="uf7003909" id="uf7003909">通过查看LongAdder中的add方法的代码，我们其实就能很容易的理解他的实现细节：</span></p>
  <p data-lake-id="u0e60dc75" id="u0e60dc75"><span data-lake-id="ufafe52d4" id="ufafe52d4">​</span><br></p>
  <pre lang="java"><code>
    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) &lt; 0 ||
                (a = as[getProbe() &amp; m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

</code></pre>
  <p data-lake-id="u6cf1ea07" id="u6cf1ea07"><br></p>
  <p data-lake-id="u1e80cbc3" id="u1e80cbc3"><span data-lake-id="u43783b68" id="u43783b68">首先就是先尝试通过CAS更新计数器base的值，如果在竞争不激烈的情况下，是可以直接更新成功的，如果直接成功那就和AtomicLong一样了。</span></p>
  <p data-lake-id="u69c662b8" id="u69c662b8"><span data-lake-id="u20690a62" id="u20690a62">​</span><br></p>
  <p data-lake-id="u6bb8352a" id="u6bb8352a"><span data-lake-id="uc93e34bf" id="uc93e34bf">但是如果更新失败，那么则是认为当前的并发竞争比较激烈，那么就会尝试通过cells数组来分散计数</span></p>
  <p data-lake-id="uf0349853" id="uf0349853"><span data-lake-id="uf6755aee" id="uf6755aee">​</span><br></p>
  <p data-lake-id="uab10eabc" id="uab10eabc"><span data-lake-id="u7e06ed5b" id="u7e06ed5b">Striped64根据线程来计算哈希，然后将不同的线程分散到不同的Cell数组的index上，然后这个线程的计数内容就会保存在该Cell的位置上面，基于这种设计，最后的总计数需要结合base以及散落在Cell数组中的计数内容。</span></p>
  <p data-lake-id="u8eb0ab98" id="u8eb0ab98"><span data-lake-id="u69c23ae4" id="u69c23ae4">​</span><br></p>
  <p data-lake-id="u73a35b22" id="u73a35b22"><span data-lake-id="u338ca1f8" id="u338ca1f8">是不是看上去和ConcurrentHashMap的分段锁很像。</span></p>
  <p data-lake-id="udd152c91" id="udd152c91"><br></p>
  <p data-lake-id="u456fc571" id="u456fc571"><span data-lake-id="u593d2064" id="u593d2064">当需要进行统计数量的时候，则需要结合base和cells一起做统计，其实就是把他们的值都加在一起。</span></p>
  <p data-lake-id="u422da57c" id="u422da57c"><br></p>
  <pre lang="java"><code>
public long sum() {
    Cell[] as = cells; Cell a;
    long sum = base;
    if (as != null) {
        for (int i = 0; i &lt; as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}
</code></pre>
  <p data-lake-id="uafd45e18" id="uafd45e18"><span data-lake-id="u95d37b53" id="u95d37b53"></span></p>
  <h3 data-lake-id="gGgIX" id="gGgIX"><span data-lake-id="u4437bbbe" id="u4437bbbe">LongAdder为什么可能会不准确</span></h3>
  <p data-lake-id="u65e0eab7" id="u65e0eab7"><br></p>
  <p data-lake-id="u23b346b7" id="u23b346b7"><span data-lake-id="u2056eb9b" id="u2056eb9b">通过查看LongAdder的代码了解了他的原理之后，很容易可以发现一个问题，那就是其实LongAdder的累加结果可能是不准的。没错，其实在JDK的源码中也明确的说了：</span></p>
  <p data-lake-id="u8a288c7f" id="u8a288c7f"><span data-lake-id="ub60acc92" id="ub60acc92">​</span><br></p>
  <blockquote data-lake-id="u3a6ac6ec" id="u3a6ac6ec">
   <p data-lake-id="u3aaba22d" id="u3aaba22d"><span data-lake-id="u8d7a7ca3" id="u8d7a7ca3">Returns the current sum. The returned value is NOT an atomic snapshot; invocation in the absence of concurrent updates returns an accurate result, but concurrent updates that occur while the sum is being calculated might not be incorporated.</span></p>
  </blockquote>
  <p data-lake-id="u4af43288" id="u4af43288"><br></p>
  <p data-lake-id="u30809818" id="u30809818"><span data-lake-id="u37928066" id="u37928066">以上，就是LongAdder的sum方法的说明，其实就是：在没有并发更新的情况下sum方法将返回准确的结果，但是在计算总和时发生的并发更新可能不会被计算进来，所以就有可能会不准确。</span></p>
  <p data-lake-id="u0e59557f" id="u0e59557f"><br></p>
  <h3 data-lake-id="bf3Mf" id="bf3Mf"><span data-lake-id="u505f402c" id="u505f402c">DoubleAdder&#x0;</span></h3>
  <p data-lake-id="uaee90d18" id="uaee90d18"><br></p>
  <p data-lake-id="u2be4e758" id="u2be4e758"><span data-lake-id="u6bbcb297" id="u6bbcb297">除了LongAdder以外，JDK 1.8中还提供了针对浮点数进行原子操作的DoubleAdder，他也是基于Striped64实现的，原理和LongAdder并无差别。</span></p>
  <p data-lake-id="u0656baa2" id="u0656baa2"><span data-lake-id="u232ec547" id="u232ec547">​</span><br></p>
  <p data-lake-id="u5ae8cd06" id="u5ae8cd06"><span data-lake-id="ua1b54d16" id="ua1b54d16">​</span><br></p>
  <h2 data-lake-id="NehxY" id="NehxY"><span data-lake-id="u19b97fbc" id="u19b97fbc">适用场景</span></h2>
  <p data-lake-id="ue21856e0" id="ue21856e0"><br></p>
  <p data-lake-id="u037b278e" id="u037b278e"><span data-lake-id="u5a7776db" id="u5a7776db">相比于AtomicLong，LongAdder性能更好，但是有一个小缺点就是有可能返回值没那么准确，但是也只是在并发极高，刚好返回sum时候有其他原子操作在进行累加的时候才会出现。</span></p>
  <p data-lake-id="u49e74c9b" id="u49e74c9b"><span data-lake-id="u26573287" id="u26573287">​</span><br></p>
  <p data-lake-id="u902243a6" id="u902243a6"><span data-lake-id="u16cfa79b" id="u16cfa79b">基于以上的特性，LongAdder比较适合于并发竞争激烈，但是对数据准确度要求并不是百分之百准确的场景，比如微博点赞、文章阅读量的统计等等场景中。</span></p>
  <p data-lake-id="uff7b9846" id="uff7b9846"><br></p>
  <h2 data-lake-id="trblx" id="trblx"><span data-lake-id="uf7f94581" id="uf7f94581">​</span><br></h2>
  <p data-lake-id="u2bb13270" id="u2bb13270"><br></p>
  <p data-lake-id="u177f985e" id="u177f985e"><span data-lake-id="u2093a1c8" id="u2093a1c8" class="lake-fontsize-12" style="color: rgba(0, 0, 0, 0.87)">​</span><br></p>
  <p data-lake-id="uf99b0957" id="uf99b0957"><span data-lake-id="u0bd4c262" id="u0bd4c262" class="lake-fontsize-12" style="color: rgba(0, 0, 0, 0.87)">​</span><br></p>
 </body>
</html>